namespace BaddiesEditor
{
    using System;
    using System.Collections.Generic;
    using System.IO;
    using System.Linq;
    using System.Xml;
    using System.Xml.Serialization;    
    using Baddies;
    using Baddies.Events;
    using Baddies.GUI;
    using Baddies.Map;  
    using Baddies.Nodes;    
    using Baddies.Scenes;
    using Baddies.Utils;
    using BaddiesEditor.PaintTools; 
    using Microsoft.Xna.Framework;
    using Microsoft.Xna.Framework.Audio;
    using Microsoft.Xna.Framework.Content;
    using Microsoft.Xna.Framework.Content.Pipeline.Serialization.Intermediate;   
    using Microsoft.Xna.Framework.GamerServices;
    using Microsoft.Xna.Framework.Graphics;
    using Microsoft.Xna.Framework.Input;
    using Microsoft.Xna.Framework.Media;    
    using Microsoft.Xna.Framework.Storage;        

    /// <summary>
    /// This is the main type for the game in the editor.
    /// </summary>
    public class EditorGame : CoreGame
    {
        /// <summary>
        /// Pointer to the draw surface.
        /// </summary>
        private IntPtr drawSurface;

        /// <summary>
        /// Reference to the main editor form.
        /// </summary>
        private EditorForm form;

        /// <summary>
        /// Reference to the MapProject.
        /// </summary>
        private MapProject mapProject;

        /// <summary>
        /// Tracks the last ID used to add a node to the root as not to repeat ID.
        /// </summary>
        private int curNodeID;

        /// <summary>
        /// Reference to the map node.
        /// </summary>
        private int mapID;

        /// <summary>
        /// Reference to the camera node.
        /// </summary>
        private int camID;

        /// <summary>
        /// Sets the reference to the editor form.
        /// </summary>
        /// <value>Reference to the editor form.</value>
        public EditorForm Form
        {
            set { this.form = value; }
        }

        /// <summary>
        /// Sets the reference to the MapProject.
        /// </summary>
        /// <value>Reference to the map project.</value>
        public MapProject MapProject
        {
            set { this.mapProject = value; }
        }

        /// <summary>
        /// Initializes a new instance of the EditorGame class.
        /// </summary>
        /// <param name="drawSurface">Reference to draw on.</param>
        public EditorGame(IntPtr drawSurface) : base()
        {
            this.curNodeID = 0;
            this.mapID = -1;
            this.camID = -1;
            this.drawSurface = drawSurface;

            graphics.PreparingDeviceSettings += 
                new EventHandler<PreparingDeviceSettingsEventArgs>(this.Graphics_PreparingDeviceSettings);

            System.Windows.Forms.Control.FromHandle(this.Window.Handle).VisibleChanged +=
                new EventHandler(this.Game1_VisibleChanged);
        }

        /// <summary>
        /// Sets the graphicsdevicemanager size.
        /// It must be the same as the picturebox to draw on or else the form does a 
        /// resize that screws up the screen-game transforms.
        /// </summary>
        /// <param name="width">Width to set to.</param>
        /// <param name="height">Height to set to.</param>
        public void SetGameSize(int width, int height)
        {
            this.graphics.PreferredBackBufferHeight = height;
            this.graphics.PreferredBackBufferWidth = width;
            this.graphics.ApplyChanges();
        }

        /// <summary>
        /// Allows the game to perform any initialization it needs to before starting to run.
        /// This is where it can query for any required services and load any non-graphic
        /// related content.  Calling base.Initialize will enumerate through any components
        /// and initialize them as well.
        /// </summary>
        protected override void Initialize()
        {
            base.Initialize();

            this.root = new Scene();
            this.root.ParentRelativePos = false;
            this.root.Enter();

            Camera cam = new Camera(this.graphics.PreferredBackBufferWidth, this.graphics.PreferredBackBufferHeight);
            cam.Position = new Vector3(0, 0, 0);
            cam.Name = "Cam";
            cam.DisplayBorder = true;

            this.camID = this.AddToRoot(cam);           
        }

        /// <summary>
        /// LoadContent will be called once per game and is the place to load
        /// all of your content.
        /// </summary>
        protected override void LoadContent()
        {
            base.LoadContent();
        }

        /// <summary>
        /// UnloadContent will be called once per game and is the place to unload
        /// all content.
        /// </summary>
        protected override void UnloadContent()
        {
            base.UnloadContent();
        }

        /// <summary>
        /// Allows the game to run logic such as updating the world,
        /// checking for collisions, gathering input, and playing audio.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        protected override void Update(GameTime gameTime)
        {            
            base.Update(gameTime);

            this.root.NodeUpdate();
        }

        /// <summary>
        /// This is called when the game should draw itself.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        protected override void Draw(GameTime gameTime)
        {
            base.Draw(gameTime);

            this.spriteBatch.Begin();
            this.root.NodeDraw(this.spriteBatch);
            this.spriteBatch.End();
        }

        /// <summary>
        /// Event capturing the construction of a draw surface and makes sure this gets redirected to
        /// a predesignated drawsurface marked by pointer drawSurface.
        /// </summary>
        /// <param name="sender"> Class that sends the message.</param>
        /// <param name="e"> Arguments of the call.</param>
        private void Graphics_PreparingDeviceSettings(object sender, PreparingDeviceSettingsEventArgs e)
        {
                e.GraphicsDeviceInformation.PresentationParameters.DeviceWindowHandle =
                this.drawSurface;
        }

        /// <summary>
        /// Occurs when the original gamewindows' visibility changes and makes sure it stays invisible.
        /// </summary>
        /// <param name="sender"> Class that sends the message.</param>
        /// <param name="e"> Arguments of the call.</param>
        private void Game1_VisibleChanged(object sender, EventArgs e)
        {
            if (System.Windows.Forms.Control.FromHandle(this.Window.Handle).Visible == true)
            {
                System.Windows.Forms.Control.FromHandle(this.Window.Handle).Visible = false;
            }
        }

        /// <summary>
        /// Adds a node to root without caring for the ID.
        /// </summary>
        /// <param name="node">Node to add.</param>
        /// <returns>The id in the root for this node.</returns>
        private int AddToRoot(Node node)
        {
            this.root.Add(this.curNodeID, node);
            this.curNodeID++;
            return this.curNodeID - 1;
        }

        /// <summary>
        /// Creates a new map.
        /// </summary>
        /// <param name="width">Width of the new map in tiles.</param>
        /// <param name="height">Height of the new map in tiles.</param>
        /// <param name="tileSize">Size of the tiles.</param>
        public void NewMap(int width, int height, int tileSize)
        {
            if (this.mapID != -1)
            {
                this.root.Remove(this.mapID);
            }

            MapGrid map = new MapGrid();
            map.CreateNewMap(width, height, tileSize);
            map.DisplayGrid = true;
            map.Name = "Map";

            this.mapID = this.AddToRoot(map);            
        }

        /// <summary>
        /// Moves the camera.
        /// </summary>
        /// <param name="dir">Direction to move in.</param>
        /// <param name="step">Length of the step.</param>
        public void MoveCam(Util.Dir dir, int step)
        {
            Camera cam = (Camera)this.root.Get(this.camID);
            cam.Move(dir, step);
        }

        /// <summary>
        /// Moves the camera to a specific position.
        /// </summary>
        /// <param name="x">X in world coordinates.</param>
        /// <param name="y">Y in world coordinates.</param>
        public void MoveCam(int x, int y)
        {
            Camera cam = (Camera)this.root.Get(this.camID);
            cam.OffsetPosition(x, y, 0);
        }

        /// <summary>
        /// Sets the name and texture for the terrain map.
        /// </summary>
        /// <param name="tileSetName">Name of the terrain file.</param>
        /// <param name="tileSet">Reference to the tileset texture.</param>
        public void SetTileset(string tileSetName, System.Drawing.Bitmap tileSet)
        {
            // Get a gfxDevice
            GetGameGfxDeviceArg args = new GetGameGfxDeviceArg();
            EventManager.GetEV.Trigger("GetGameGfxDevice", this, args);

            // Transform the bitmap to texture
            using (MemoryStream s = new MemoryStream())
            {
               tileSet.Save(s, System.Drawing.Imaging.ImageFormat.Png);
               s.Seek(0, SeekOrigin.Begin);
               Texture2D tx = Texture2D.FromStream(args.GraphicsDevice, s);

                // Set the new texture
               ((MapGrid)this.root.Get(this.mapID)).SetTileset(tileSetName, tx);
            }            
        }

        /// <summary>
        /// Given a clicked point in the gameViewport and a tilePosition in the 
        /// tileSet, it updates the map as needed.
        /// </summary>
        /// <param name="tool">Tool to use for painting.</param>
        public void HandleMapPaint(PaintTool tool)
        {
            MapGrid map = (MapGrid)this.root.Get(this.mapID);

            tool.Use(map, this.mapProject.TileSize);
        }

        /// <summary>
        /// Saves the game map.
        /// </summary>
        /// <param name="name">Name to save it with.</param>
        public void SaveMap(string name)
        {
            NodeSerializer serializer = new NodeSerializer();
            serializer.Serialize(this.root, name);
        }

        /// <summary>
        /// Loads the map.
        /// </summary>
        /// <param name="name">File URI to load.</param>
        public void LoadMap(string name)
        {
            NodeSerializer serializer = new NodeSerializer();
            this.root = serializer.Deserialize(name);            
        }

        /// <summary>
        /// Closes the game and ends execution.
        /// </summary>
        public void ShutDown()
        {
            this.Exit();
        }

        /// <summary>
        /// Moddifies the map size (number of columns and rows).
        /// </summary>
        /// <param name="editMapSizeArg">Data on how to modify the map.</param>
        public void EditMapSize(Events.EditMapSizeArg editMapSizeArg)
        {
            MapGrid map = (MapGrid)this.root.Get(this.mapID);
            if (editMapSizeArg.NumberLines > 0)
            {
                map.AddLines(editMapSizeArg.Side, editMapSizeArg.NumberLines);
            }
            else if (editMapSizeArg.NumberLines < 0)
            {
                map.RemoveLines(editMapSizeArg.Side, editMapSizeArg.NumberLines);
            }
        }
    }
}
